package com.zhouzhou.fastjava.excel;

import com.zhouzhou.fastjava.excel.exception.ExceededWriteRowsException;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.jexl3.JexlBuilder;
import org.apache.commons.jexl3.JexlEngine;
import org.apache.commons.jexl3.JexlExpression;
import org.apache.commons.jexl3.ObjectContext;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.DateFormatUtils;
import org.apache.poi.POIXMLProperties;
import org.apache.poi.hssf.util.HSSFColor;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.ss.util.WorkbookUtil;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;

import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.function.Function;
import java.util.stream.Collectors;

public abstract class ExcelWriter<T> {

    private static final String DEFAULT_DATE_FORMAT_PATTERN = "yyyy-MM-dd HH:mm:ss";

    private static final String FONT_NAME = "Segoe UI";

    private long maxWriteRows = Long.MAX_VALUE;

    private final XSSFWorkbook book;

    private final Sheet sheet;

    private final String title;

    private final CellStyle dataCellStyle;

    private int rowAt;

    private int headerSize;

    private String creator = "fastjava";

    private String dateFormatPattern = DEFAULT_DATE_FORMAT_PATTERN;

    private Function<Object, Object> objectProcessor = r -> r;

    public ExcelWriter(String title) {
        this.title = title;
        this.book = new XSSFWorkbook();
        this.sheet = book.createSheet(WorkbookUtil.createSafeSheetName(title));
        {
            this.dataCellStyle = book.createCellStyle();
            Font font = book.createFont();
            font.setFontHeightInPoints((short) 9);
            font.setFontName(FONT_NAME);
            this.dataCellStyle.setFont(font);
        }
    }

    public ExcelWriter<T> header(ExcelHeader[] headers) {

        this.headerSize = headers.length;
        Row row = sheet.createRow(rowAt++);
        for (int i = 0; i < headerSize; i++) {
            Cell cell = row.createCell(i);
            ExcelHeader header = headers[i];
            cell.setCellValue(header.getName());

            CellStyle cellStyle = book.createCellStyle();
            Font font = book.createFont();
            font.setBold(true);
            font.setColor(HSSFColor.BLACK.index);
            font.setFontHeightInPoints((short) 9);
            font.setFontName(FONT_NAME);
            cellStyle.setFont(font);

            cell.setCellStyle(cellStyle);
            sheet.setColumnWidth(i, header.getWidth() * 256);
        }
        sheet.createFreezePane(0, 1);
        return this;
    }

    public ExcelWriter<T> writeByExpressions(Collection<T> srcRows, String[] els) {

        if (CollectionUtils.isEmpty(srcRows)) {
            return this;
        }
        if (srcRows.size() > maxWriteRows) {
            throw new ExceededWriteRowsException(maxWriteRows);
        }
        if (els.length != headerSize) {
            throw new IllegalArgumentException("the size of headers must be equals the size of els");
        }

        JexlEngine jexlEngine = new JexlBuilder().create();
        List<JexlExpression> jes = Arrays.stream(els).map(el -> jexlEngine.createExpression(el)).collect(Collectors.toList());
        srcRows.stream().forEach(srcRow -> {
            Row row = sheet.createRow(rowAt++);
            for (int i = 0; i < headerSize; i++) {
                Cell cell = row.createCell(i);
                cell.setCellStyle(dataCellStyle);
                this.writeCellValue(cell, jes.get(i).evaluate(new ObjectContext<>(jexlEngine, srcRow)));
            }
        });
        return this;
    }

    public ExcelWriter<T> write(Collection<T> srcRows, Function<T, Object[]> transfer) {

        if (CollectionUtils.isEmpty(srcRows)) {
            return this;
        }
        if (srcRows.size() > maxWriteRows) {
            throw new ExceededWriteRowsException(maxWriteRows);
        }
        srcRows.stream().forEach(srcRow -> {
            Row row = sheet.createRow(rowAt++);
            Object[] dataRow = transfer.apply(srcRow);
            if (dataRow.length != headerSize) {
                throw new IllegalArgumentException("the size of headers must be equals the size of row");
            }
            for (int i = 0; i < headerSize; i++) {
                Cell cell = row.createCell(i);
                cell.setCellStyle(dataCellStyle);
                this.writeCellValue(cell, dataRow[i]);
            }
        });
        return this;
    }

    private void writeCellValue(Cell cell, Object val) {
        if (val == null) {
            cell.setCellValue(StringUtils.EMPTY);
            return;
        }
        val = objectProcessor.apply(val);
        if (val instanceof Number) {
            cell.setCellValue(Double.valueOf(val.toString()));
        } else if (val instanceof Date) {
            cell.setCellValue(DateFormatUtils.format((Date) val, dateFormatPattern));
        } else {
            cell.setCellValue(val.toString());
        }
    }

    private void settingProperties() {
        POIXMLProperties bookProperties = book.getProperties();
        POIXMLProperties.CoreProperties coreProperties = bookProperties.getCoreProperties();
        coreProperties.setCreator(creator);
        coreProperties.setTitle(title);
    }

    public Workbook get() {
        settingProperties();
        return book;
    }

    public ExcelWriter<T> maxWriteRows(long maxWriteRows) {
        this.maxWriteRows = maxWriteRows;
        return this;
    }

    public ExcelWriter<T> dateFormatPattern(String dateFormatPattern) {
        this.dateFormatPattern = dateFormatPattern;
        return this;
    }

    public ExcelWriter<T> objectProcessor(Function<Object, Object> objectProcessor) {
        this.objectProcessor = objectProcessor;
        return this;
    }

    public ExcelWriter<T> creator(String creator) {
        this.creator = creator;
        return this;
    }

}
